package com.k2data.k2app.service;

import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.k2data.k2app.common.K2Assert;
import com.k2data.k2app.common.MyConstant;
import com.k2data.k2app.domain.po.JobTaskDO;
import com.k2data.k2app.domain.po.ModelDO;
import com.k2data.k2app.domain.po.TaskDTO;
import com.k2data.k2app.domain.po.TaskDeviceDO;
import com.k2data.k2app.domain.vo.Device;
import com.k2data.k2app.domain.vo.Job;
import com.k2data.k2app.domain.vo.LabelValueKeyObject;
import com.k2data.k2app.domain.vo.Model;
import com.k2data.k2app.exception.K2ResponseException;
import com.k2data.k2app.mapper.TaskDeviceMapper;
import com.k2data.k2app.mapper.TaskMapper;
import com.k2data.k2app.mapper.TaskQuartzMapper;
import com.k2data.k2app.util.DateFormatterUtil;
import com.k2data.k2app.util.schedule.ScheduleJob;
import com.k2data.k2app.util.schedule.ScheduleJobService;
import com.k2data.k2app.utils.StringUtils;
import lombok.extern.log4j.Log4j2;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.*;

/**
 * @author clb
 */
@Log4j2
@Service
public class TaskService {
    @Autowired
    private ModelService modelService;
    @Autowired
    private TaskMapper taskMapper;
    @Autowired
    private TaskDeviceMapper taskDeviceMapper;
    @Autowired
    private JobTaskService jobTaskService;
    @Autowired
    private JobService jobService;
    @Autowired
    private DeviceService deviceService;
    @Autowired
    private ScheduleJobService scheduleJobService;
	@Autowired
	private TaskQuartzMapper taskQuartzMapper;


    /**
     * 初始化任务的信息
     * @param taskDO
     * @throws Exception
     */
    public void initTask(TaskDTO taskDO) throws Exception {
        //检验任务重复性
        K2Assert.existTask(taskDO);
        //测点／测点变量
        List<LabelValueKeyObject> varData = taskDO.getVarData();
        StringBuffer fields = new StringBuffer("");
        for(LabelValueKeyObject labelValueObject:varData){
            if(!StringUtils.isEmpty(fields)){
                fields.append("#");
            }
            fields.append(labelValueObject.getKey());
        }
        taskDO.setKmxVariableName(fields.toString());
        //插入kmx的字段
        if(taskDO.getVarDataCalculate() != null){
            taskDO.setKmxSumField(taskDO.getVarDataCalculate().getKey());
        }
    }

    /**
     * 创建任务
     *
     * <p>创建任务，同时根据选择的模板模型，在pas上创建模型的副本，作为和此任务的关联模型
     * @param taskDO
     * @return
     * @throws Exception
     */
    public long create(TaskDTO taskDO) throws Exception {
        /**
         * 修改任务的属性值
         */
        initTask(taskDO);
        //复制模型
        Long modelId = modelService.copyAndEdit(taskDO);
        taskDO.setModelIdCopy(modelId);
       return insert(taskDO);
    }

    /**
     * 更新任务
     *
     * <p>模型更改，要先删除之前模型的信息,模型每更改，只修改模型的信息
     * @param taskDO
     * @return
     * @throws Exception
     */
    public long updateTask(TaskDTO taskDO) throws Exception{
        initTask(taskDO);

        TaskDTO taskDO1 = taskMapper.get(taskDO.getId());
        //模型没改动
        if(String.valueOf(taskDO1.getModelId()).equals(String.valueOf(taskDO.getModelId()))){
            taskDO.setModelIdCopy(taskDO1.getModelIdCopy());
            //修改模型表信息
            modelService.editProject(taskDO);
            return update(taskDO);
        }
        //删除之前模型的一切信息
        modelService.delete(taskDO.getModelIdCopy());
        Long modelCopyId = modelService.copyAndEdit(taskDO);
        taskDO.setModelIdCopy(modelCopyId);
        return update(taskDO);
    }


    /**
     * 更新数据库
     * @param taskDO
     * @return
     */
    @Transactional
    public int update(TaskDTO taskDO) throws Exception {
        taskDO.setAction(Joiner.on(",").join(taskDO.getActions()));
        taskDO.setFre();
        taskMapper.updateByKey(taskDO);
        taskDeviceMapper.deleteByTaskId(taskDO.getId());
        insertDeviceTaskBatch(taskDO);
        //启动定时任务
        autoTask(taskDO);
        return 1;
    }

    public long insert(TaskDTO taskDTO) throws Exception {
        // 输出结果存储格式
        taskDTO.setAction(Joiner.on(",").join(taskDTO.getActions()));
        // 频率存储格式
        taskDTO.setFre();
        long id = insertTask(taskDTO);
        taskDTO.setId(id);
        //启动定时任务
        autoTask(taskDTO);
        //任务和设备关联
        insertDeviceTaskBatch(taskDTO);
        //任务的模型信息
        ModelDO modelDO = new ModelDO();
        modelDO.setModelId(taskDTO.getModelIdCopy());
        modelDO.setUseful(MyConstant.USEFUL);
        modelService.insertRecord(modelDO);
        return taskDTO.getId();
    }

    @Transactional
    public long insertTask(TaskDTO taskDTO){
        taskDTO.setStartDate(taskDTO.getStartDate().plusHours(8));
        taskDTO.setEndDate(taskDTO.getEndDate().plusHours(8));
        taskMapper.insertRecode(taskDTO);
        return taskDTO.getId();
    }


    public TaskDTO get(Long id) throws Exception{
        TaskDTO taskDO =  taskMapper.get(id);
        taskDO.setActions(taskDO.getAction().split(","));
        String fre = taskDO.getFrequency();
        String[] freP = fre.split("_");
        if(freP.length>1){
            taskDO.setFrequency(freP[0]);
            taskDO.setPrefix(freP[1]);
        }
        setPointDataVarData(taskDO);
        setDevicePart(taskDO);
        setModelInfo(taskDO);
        setCustomInfo(taskDO);
        setDevice(taskDO);
        return taskDO;
    }

    public TaskDTO getWithOutDetail(Long id) throws Exception{
        TaskDTO taskDO =  taskMapper.get(id);
        taskDO.setActions(taskDO.getAction().split(","));
        return taskDO;
    }

    public Integer selectCount(TaskDTO taskDO){
        int i = taskMapper.selectCount(taskDO);
        return i;
    }


    public List<TaskDTO> listWithDevice(TaskDTO taskDO, boolean isHaveModel) throws Exception{
        List<TaskDTO> list = taskMapper.listWithDevices(taskDO);
        for(TaskDTO item:list){
            try{
                //结果通知格式
                setDevicePart(item);
                setModelInfoSimple(item);
                setDevice(item);
            }catch (Exception e){
                e.printStackTrace();
            	log.error(e.getMessage());
                continue;
            }
        }
        //是否关联模型
        if(isHaveModel){
            for (TaskDTO healthTask : list) {
               setModelInfo(healthTask);
            }
        }
        return list;
    }

    public void setDevice(TaskDTO taskDTO) throws Exception {
        List<Device> devices = taskDTO.getDevice();
        for(Device device: devices){
            Long id = device.getDeviceId();
            Map devicePartObject = deviceService.getDeviceById(id);
            if(devicePartObject.size()==0){ continue;}
            device.setDeviceNo(devicePartObject.get("deviceNo").toString());
            device.setDeviceName(devicePartObject.get("name").toString());
        }
    }

    public void setCustomInfo(TaskDTO taskDTO) {
        Long customerId = taskDTO.getCustomerId();
        if(customerId!=null){
            LabelValueKeyObject devicePart = new LabelValueKeyObject();
            devicePart.setKey(customerId.toString());
            Map devicePartObject = null;
            try {
                devicePartObject = deviceService.getCustomerById(customerId);
                if(devicePartObject.size()==0){ return;}
                devicePart.setLabel(devicePartObject.get("name").toString());
            } catch (Exception e) {
                log.error(String.format("客户信息获取报错!customer_id:%s message:%s",customerId,e.getMessage()));
            } finally {
                taskDTO.setCustomer(devicePart);
            }
        }else{
            LabelValueKeyObject devicePart = new LabelValueKeyObject();
            devicePart.setKey("all");
            devicePart.setLabel("全部");
            taskDTO.setCustomer(devicePart);
        }
    }

    /**
     * 给任务关联模型信息
     * @param healthTask
     * @throws Exception
     */
    public void setModelInfo(TaskDTO healthTask) throws Exception {
        if(healthTask.getModelId() != null){
            Optional<Model> model = Optional.ofNullable(modelService.get(healthTask.getModelId(),false));
            if(model.isPresent()) {
                //作业个数
                if (healthTask.getModelIdCopy() != null) {
                    List<Job> jobs = jobService.listJobs(null, healthTask.getModelIdCopy(),1,1000,null);
                    model.get().setJobCount(jobs.size());
                }
                model.get().setUseFul(modelService.isUseful(healthTask.getModelIdCopy()));
                healthTask.setModel(model.get());
            }
        }
    }
    public void setModelInfoSimple(TaskDTO healthTask) throws Exception {
        Model model = new Model();
        List<Job> jobs = jobService.listJobs(null, healthTask.getModelIdCopy(),1,1000,null);
        model.setJobCount(jobs.size());
        model.setUseFul(modelService.isUseful(healthTask.getModelIdCopy()));
        model.setUseFulTemplate(modelService.isUseful(healthTask.getModelId()));
        healthTask.setModel(model);
    }

    /**
     * 给任务的测点和测点变量赋值
     * @param item
     * @return
     * @throws K2ResponseException
     */
    public TaskDTO setPointDataVarData(TaskDTO item) throws K2ResponseException {
        if(item.getKmxVariableName()!=null){
            String[] ids = item.getKmxVariableName().split("#");
            if(ids.length>0){
                // 如果设备为多个，测点和通道取从最后一个变量中获取
                String deviceId =ids[ids.length-1].split("_")[0];
                String monitorPointId =ids[ids.length-1].split("_")[1];
                String channelId =ids[ids.length-1].split("_")[2];
                Integer channelid = channelId.equals("all")?null:Integer.parseInt(channelId);
                Map point = new HashMap();
//                if(deviceId.equals("all")){
//                    Map map = deviceService.getTempplatePoint(Long.parseLong(monitorPointId));
//                    if(map != null && map.size()>0){
//                        point.put("sensorName",map.get("sensorName").toString());
//                        point.put("tMonitorPointId",map.get("id").toString());
//                        point.put("mointorPointId",map.get("id").toString());
//                        point.put("deviceId","all");
//                        point.put("children",new ArrayList<>());
//                    }
//                }
//                else{
//                    Map map = deviceService.getDevicePointGroup(Long.parseLong(deviceId),Long.parseLong(monitorPointId),
//                            null,true,2);
//                    if(map != null && map.size()>0){
//                        point.put("sensorName",map.get("sensorName").toString());
//                        point.put("tMonitorPointId",map.get("tmonitorPointId").toString());
//                        point.put("mointorPointId",map.get("id").toString());
//                        point.put("deviceId",map.get("deviceId").toString());
//                        point.put("children",map.get("monitorDTOs"));
//                    }
//                }
                Map map = deviceService.getTempplatePoint(Long.parseLong(monitorPointId));
                if(map != null && map.size()>0){
                    point.put("sensorName",map.get("sensorName").toString());
                    point.put("tMonitorPointId",map.get("id").toString());
                    point.put("mointorPointId",map.get("id").toString());
                    point.put("deviceId","all");
                    point.put("children",new ArrayList<>());
                }

                item.setPoint(point);
                if(!channelId.equals("all")){
                    List monitorDTOs = (List) point.get("children");
                    for(int i = 0 ;i<monitorDTOs.size();i++){
                        Map monitorDTO = (Map) monitorDTOs.get(i);
                        if(Integer.parseInt(monitorDTO.get("channelId").toString()) == channelid){
                            Map channel = new HashMap();
                            channel.put("key",monitorDTO.get("channelId").toString());
                            channel.put("value",monitorDTO.get("nameEn").toString());
                            channel.put("label",monitorDTO.get("channelName").toString());
                            channel.put("sensorName",monitorDTO.get("sensorName").toString());
                            item.setChannel(channel);
                        }
                    }
                }
                List<LabelValueKeyObject> varDatas = Lists.newArrayList();
                for(String id:ids){
                    String[] idsOne = id.split("_");
                    if(idsOne.length==4){
                        String varibleId = idsOne[3];
                        Map map1 = deviceService.getTemplateVariablesById(Long.parseLong(varibleId));
                        if (map1 != null && map1.size()>0){
                            LabelValueKeyObject labelValueKeyObject = new LabelValueKeyObject();
                            labelValueKeyObject.setKey(idsOne[0]+"_"+idsOne[1]+"_"+idsOne[3]);
                            labelValueKeyObject.setLabel(map1.get("variableName").toString());
                            labelValueKeyObject.setValue(map1.get("variableType").toString()+","+map1.get("kmxVariableNameEn").toString());
                            varDatas.add(labelValueKeyObject);
                        }
                    }
                }
                item.setVarData(varDatas);
            }
        }
        if(item.getKmxSumField()!=null){
            String[] ids = item.getKmxSumField().split("_");
            if(ids.length>0){
                String templatePointId = ids[0];
                String varibleId = ids[1];
                Map map = deviceService.getTempplatePoint(Long.parseLong(templatePointId));
                Map point = new HashMap();
                if(map != null && map.size()>0) {
                    point.put("key",map.getOrDefault("id","").toString());
                    point.put("value",map.getOrDefault("id","").toString());
                    point.put("label",map.getOrDefault("sensorName","").toString());
                }
                item.setPointCalculate(point);
                Map mapVarible = deviceService.getTemplateVariablesById(Long.parseLong(varibleId));
                if(mapVarible !=null && mapVarible.size()>0){
                    LabelValueKeyObject labelValueKeyObject = new LabelValueKeyObject();
                    labelValueKeyObject.setKey(item.getKmxSumField());
                    labelValueKeyObject.setLabel(mapVarible.get("variableName").toString());
                    labelValueKeyObject.setValue(mapVarible.get("variableType").toString()+","+mapVarible.get("kmxVariableNameEn").toString());
                    item.setVarDataCalculate(labelValueKeyObject);
                }
            }
        }

        return item;
    }

    /**
     * 给任务的设备部件赋值
     * @param item
     * @return
     * @throws K2ResponseException
     */
    public TaskDTO setDevicePart(TaskDTO item) {
        String devicePartId = item.getDeviceParts();
        if(!StringUtils.isEmpty(devicePartId)){
            Map devicePartObject = null;
            try {
                if(devicePartId == null || devicePartId.equals("undefined")) {return item;}
                devicePartObject = deviceService.getTemplateDeviceById(Long.parseLong(devicePartId));
            } catch (K2ResponseException e) {
                log.error(String.format("部件信息获取报错!devicePartId:%s message:%s",devicePartId,e.getMessage()));
            }
            if(devicePartObject.size()==0) {return item;}
            LabelValueKeyObject devicePart = new LabelValueKeyObject();
            devicePart.setKey(devicePartObject.get("id").toString());
            devicePart.setLabel(devicePartObject.get("name").toString());
            item.setDevicePart(devicePart);
        }
        return item;
    }
    /**
     * 删除任务
     * 同时删除任务关联的复制模型(pas)，模型和作业关联记录(local)，模型有效性记录(local)，模型的作业(pas)
     * @param id 任务id
     * @return
     * @throws Exception
     */
    @Transactional
    public int delete(Long id) throws Exception{
        TaskDTO taskDO = taskMapper.get(id);
        //1 删除任务和设备关联关系
        taskDeviceMapper.deleteByTaskId(id);
        log.debug("任务"+id+"和设备关联关系解除成功");
        modelService.delete(taskDO.getModelIdCopy());
        log.debug("任务模型信息清除完成");
        //4 删除任务
        taskMapper.deleteById(id);
        log.debug("任务"+id+"删除成功");
        //删除任务的定时信息
        if(taskDO.getTaskType().equals("auto")){
            ScheduleJob byTaskId = taskQuartzMapper.getByTaskId(id);
            if(byTaskId!=null){
                scheduleJobService.removeJob(byTaskId);
                taskQuartzMapper.delete(byTaskId);
            }
        }
        return 1;
    }

	@Transactional
    public void insertDeviceTaskBatch(TaskDTO taskDO) throws Exception {
        List<TaskDeviceDO> taskDeviceDOs = new ArrayList<>();
        if(taskDO.getCustomer() != null){
            if(taskDO.getDevice() != null && taskDO.getDevice().size()>0){
                taskDO.getDevice().stream().forEach(device -> {
                    TaskDeviceDO taskDeviceDO = new TaskDeviceDO();
                    taskDeviceDO.setTaskId(taskDO.getId());
                    taskDeviceDO.setDeviceId(device.getDeviceId());
                    taskDeviceDOs.add(taskDeviceDO);
                });
            }
//            else{
//                List devicesByCustomer = deviceService.getDevicesByCustomer(taskDO.getCustomerId());
//                for(int i=0;i<devicesByCustomer.size();i++){
//                    Map device = (Map) devicesByCustomer.get(i);
//                    TaskDeviceDO taskDeviceDO = new TaskDeviceDO();
//                    taskDeviceDO.setTaskId(taskDO.getId());
//                    taskDeviceDO.setDeviceId(Long.parseLong(device.get("id").toString()));
//                    taskDeviceDOs.add(taskDeviceDO);
//                }
//            }
            if(taskDeviceDOs.size()>0){
               taskDeviceMapper.insertBatch(taskDeviceDOs);
            }
        }

    }

    public List<TaskDTO> queryList(TaskDTO taskDTO){
        return taskMapper.queryList(taskDTO);
    }

    /**
     * 启动任务
     * @param taskId 任务id
     * @param type 启动类型(自动:auto 手动:manual)
     * @return
     * @throws Exception
     */
    public Long run(Long taskId,String type) throws Exception{
            System.out.println("进入定时任务－－－");
            K2Assert.existRelationTaskId(taskId);
            TaskDTO taskDO = get(taskId);

            //该任务启动的最新一条作业的时间条件(判断是否超出了时间条件)
        LocalDateTime[] times = new LocalDateTime[0];
        try {
            times = checkAndGetTime(taskDO,type);
        } catch (K2ResponseException e) {
            //任务过期或者报错，则删除定时任务
            ScheduleJob scheduleJobOld = taskQuartzMapper.getByTaskId(taskId);
            if(scheduleJobOld != null){
                scheduleJobService.removeJob(scheduleJobOld);
                taskQuartzMapper.delete(scheduleJobOld);
            }
            throw e;
        }

        String start = times[0].format(DateFormatterUtil.dateTimeFormatter);
        String end = times[1].format(DateFormatterUtil.dateTimeFormatter);
        Long modelId = taskDO.getModelIdCopy();
        Long jobId = modelService.run(modelId,start,end);
        JobTaskDO jobTaskDO = new JobTaskDO();
        jobTaskDO.setTaskId(taskId);
        jobTaskDO.setProjectId(modelId);
        jobTaskDO.setJobId(jobId);
        jobTaskDO.setStartTime(times[0]);
        jobTaskDO.setEndTime(times[1]);
        jobTaskDO.setStatus(0);
        jobTaskService.insert(jobTaskDO);
        System.out.println("本次任务执行完毕－－－");
        return jobId;
    }

    /**
     * 自动任务
     *
     * 任务刚创建时，判断任务有效时间和当前时间的间隔
     *
     * 如果已经超过自动频率时间，则启动一次任务，以当前
     *
     *
     *
     * @param taskDO
     * @throws Exception
     */
    public Long autoTask (TaskDTO taskDO) throws Exception {
        //删除定时任务
        ScheduleJob scheduleJobOld = taskQuartzMapper.getByTaskId(taskDO.getId());
        if(scheduleJobOld != null){
            scheduleJobService.removeJob(scheduleJobOld);
            taskQuartzMapper.delete(scheduleJobOld);
        }

        if(!taskDO.getTaskType().equals("auto")){
            return taskDO.getId();
        }
        String fre = taskDO.getFrequency();
        String[] fres = fre.split("_");
        String cron = getCron(fres[1],fres[0],taskDO.getStartDate());
        ScheduleJob scheduleJob = new ScheduleJob();
        scheduleJob.setJobDesc(String.format("task(id:%s) is running",taskDO.getId()));
        scheduleJob.setJobName(String.valueOf(taskDO.getId()));
        scheduleJob.setJobGroup(MyConstant.JOB_GROUP_NAME);
        scheduleJob.setCronExpression(cron);
        scheduleJob.setStartDate(taskDO.getStartDate());
        scheduleJobService.addScheduleJob(scheduleJob);
         log.debug("定时任务保存记录");
        List<ScheduleJob> list = taskQuartzMapper.queryList(scheduleJob);
        if(list.size() == 0){
            taskQuartzMapper.insertRecord(scheduleJob);
        }else {
            scheduleJob.setId(list.get(0).getId());
            taskQuartzMapper.updateByPrimaryKeySelective(scheduleJob);
        }
        return taskDO.getId();
    }

    String getCron (String type,String fre,LocalDateTime localDateTime){
        String cron = "";
        int day = localDateTime.getDayOfMonth();
        int hour = localDateTime.getHour();
        int minute = localDateTime.getMinute();
        System.out.println("day:"+day+"  hour:"+hour+"  minute:"+minute);
        switch (type){
            case "m":
                cron = String.format("0 */%s * * * ? *",fre);
                break;
            case "h":
                cron = String.format("0 %s 0/%s * * ? *",minute,fre);
                break;
            case "d":
                cron = String.format("0 %s %s 1/%s * ? *",minute,hour,fre);//每天0点
                break;
            case "mo":
                cron = String.format("0 %s %s %s 1/%s ?",minute,hour,day,fre);//从1月1号开始，每s个月执行一次
                break;
            default:
                cron = String.format("0 */%s * * * ? *",fre);
                break;
        }
        return cron;
    }

    /**
     * 检查任务时间范围内还能否启动任务
     * @param taskDO
     * @return 启动任务开始，结束时间
     * @throws K2ResponseException
     */
    public LocalDateTime[] checkAndGetTime(TaskDTO taskDO,String type) throws K2ResponseException {
        // 任务有效性判断
        K2Assert.isUseful(taskDO);
        //该任务启动的最新一条作业的时间条件(判断是否超出了时间条件)
        JobTaskDO jobTaskDOLatest = jobTaskService.getLatestByTaskId(taskDO.getId());
        LocalDateTime thisJobStart = null;
        LocalDateTime thisJobEnd = null;
        //初次启动任务
        //检查任务有效时间还能否启动任务
        K2Assert.isCanRun(jobTaskDOLatest, taskDO);
        if(jobTaskDOLatest == null || jobTaskDOLatest.getEndTime() == null){
            thisJobStart = taskDO.getStartDate();
            //有效结束时间小于当前时间,则任务endtime 为 有效结束时间,否则为当前时间(目的: 避免到当前时间过长,导致任务运行时间加长)
            thisJobEnd = taskDO.getEndDate().isBefore(LocalDateTime.now())? taskDO.getEndDate():LocalDateTime.now();
        }else{
            //本次任务的开始时间
            thisJobStart = jobTaskDOLatest.getEndTime();
            //本次任务的结束时间
            if(type !=null && type.equals("manual")){
                thisJobEnd = taskDO.getEndDate().isBefore(LocalDateTime.now())? taskDO.getEndDate():LocalDateTime.now();
            }else{
                String[] fre = taskDO.getFrequency().split("_");
                if(fre.length>1){
                    thisJobEnd = setEndTime(Integer.parseInt(fre[0]),fre[1],thisJobStart);
                }else{
                    thisJobEnd = setEndTime(Integer.parseInt(fre[0]),taskDO.getPrefix(),thisJobStart);
                }
            }
        }
        LocalDateTime[] times = new LocalDateTime[2];
        times[0] = thisJobStart;
        times[1] = thisJobEnd;
        return times;
    }


    /**
     * 获取分析作业的结束时间
     * 1 如果结束时间在当前时间之后,则获取当前时间,
     * 2 如果结束时间不是整数,则获取分钟整数
     * @param frequency
     * @param unit
     * @param oldTime
     * @return
     */
    public LocalDateTime setEndTime(Integer frequency,String unit,LocalDateTime oldTime){
        LocalDateTime localDateTime = null;
        if(unit.equals("h")){
            localDateTime = oldTime.plusHours(frequency);
        }else {
            localDateTime = oldTime.plusMinutes(frequency);
        }
        return localDateTime;
    }
}
